package control;

import java.util.Arrays;

import model.Board;
import model.Player;
import view.Boundary;
import fields.Field;
import fields.Ownable;
import fields.Street;

public class TurnController {
	private enum Option {
		STATUS {@Override public String toString(){return "Show status";}},
		BUYHOUSE {@Override public String toString(){return "Buy a house";}},
		SELLHOUSE {@Override public String toString(){return "Sell a house";}},
		PAWN {@Override public String toString(){return "Pawn a lot";}},
		UNPAWN {@Override public String toString(){return "Unpawn a lot";}},
		ROLLDICE {@Override public String toString(){return "Roll the dice";}},
		ENDTURN {@Override public String toString(){return "End your turn";}}}
	private boolean hasRolled;
	private boolean endTurn;
	private int doubleCounter;

	public void takeTurn(Player player){
		takeTurn(player, false);
	}
	@SuppressWarnings("hiding")
	public void takeTurn(Player player, boolean hasRolled){
		this.hasRolled = hasRolled;
		this.endTurn = false;
		this.doubleCounter = 0;
		
		//Show status
		
		if(player.getJailedTime() >= 0){
			turnInJail(player);
		}else{
			while(!this.endTurn && player.getJailedTime() < 0){
				Option[] options = getOptions(player);
				Option selection = getSelection(player, options);
				switch(selection){
					case STATUS: break;
					case BUYHOUSE: buyHouse(player); break;
					case SELLHOUSE: sellHouse(player); break;
					case PAWN: pawn(player); break;
					case UNPAWN: unPawn(player); break;
					case ROLLDICE: rollDice(player); break;
					case ENDTURN: this.endTurn = true; break;
				}
			}
		}
		if (player.calculatePlayerWorth() < 0) {
			declareBankruptcy(player);
		}
	}
	private void declareBankruptcy(Player player) {
		player.setIsPlaying(false);
		for (Ownable o : player.getOwned()) {
			if (o != null) {
				if (o instanceof Street){
					((Street)o).setHouseCount(0);
				}
				o.setOwner(null);
			}
		}
		Arrays.fill(player.getOwned(), null);
		while (player.hasPardonCard()) { player.removePardonCard(); }
		Boundary.removeCar(player.getName());
	}
	private void turnInJail(Player player) {
		player.incrementJailedTime();
		String[] buttons;
		if(player.hasPardonCard()) {
			buttons = new String[3];
			buttons[2] = "Use a Pardon-card to get out of jail";
		} else {
			buttons = new String[2];
		}
		buttons[0] = "Pay " + Board.getOutOfJailPrice + " to get out of jail";
		buttons[1] = "Roll dice - Two of a kind to get out of jail";
		String selected = Boundary.getPressedButton(
			player.getName() +
			"\nSelect an option", buttons);
		int selection = 0;
		if (selected.equals(buttons[0])){ selection = 0;}
		else if (selected.equals(buttons[1])){ selection = 1;}
		else if (selected.equals(buttons[2])){ selection = 2;}
		switch(selection) {
			case 0:
				payToGetOutOfJail(player);
				break;
			case 1:
				rollToGetOutOfJail(player);
				break;
			case 2:
				useCardToGetOutOfJail(player);
				break;
		}
	}
	private void payToGetOutOfJail(Player player) {
		player.withdraw(Board.getOutOfJailPrice);
		player.resetJailedTime();
		Boundary.showMessage("You have paid to get out of jail," +
			"\nplease take your turn.");
		takeTurn(player);
	}
	private void rollToGetOutOfJail(Player player) {
		Board.cup.roll();
		if(Board.cup.isSame()) {
			player.resetJailedTime();
			rollDice(player, Board.cup.getSum());
			Boundary.showMessage("You rolled two of the same to get out of jail," +
				"\nplease take your turn.");
			takeTurn(player, true);
		} else {
			if(player.getJailedTime() >= 3) {
				player.withdraw(Board.getOutOfJailPrice);
				player.resetJailedTime();
				rollDice(player, Board.cup.getSum());
				Boundary.showMessage("You did not roll two of the same to get out of jail," +
					"\nbut you have overstayed your welcome in jail." +
					"\nPlease take your turn.");
				takeTurn(player, true);
			} else {
				Boundary.showMessage("You did not roll two of the same to get out of jail." +
					"\nYou have now stayed in jail for " + player.getJailedTime() + " turns." +
					"\nThe next player gets his turn.");
			}
		}
	}
	private void useCardToGetOutOfJail(Player player) {
		player.removePardonCard();
		player.resetJailedTime();
		Boundary.showMessage("You have used a pardon card to get out of jail," +
			"\nplease take your turn.");
		takeTurn(player);
	}
	
	private Option[] getOptions(Player player){
		boolean[] validOptions = new boolean[Option.values().length];
		validOptions[Option.STATUS.ordinal()] = false; //STATUS is disabled
		validOptions[Option.BUYHOUSE.ordinal()] = canBuild(player);
		validOptions[Option.SELLHOUSE.ordinal()] = hasSellableHouses(player);
		validOptions[Option.PAWN.ordinal()] = canPawn(player);
		validOptions[Option.UNPAWN.ordinal()] = canUnPawn(player);
		validOptions[Option.ROLLDICE.ordinal()] = canRollDice();
		validOptions[Option.ENDTURN.ordinal()] = canEndTurn(player);

		int optionCount = 0;
		for(boolean valid : validOptions){ if(valid) { optionCount++; } }
		
		Option[] options = new Option[optionCount];
		int nextOptionIndex = 0;
		for (int i = 0; i < validOptions.length; i++) {
			if(validOptions[i]){
				options[nextOptionIndex++] = Option.values()[i];
			}
		}
		return options;
	}
	private Option getSelection(Player player, Option[] options) {
		// If you get a null pointer exception here, 
		//  something else is wrong.
		String[] strings = new String[options.length];
		for (int i = 0; i < options.length; i++) {
			strings[i] = options[i].toString();
		}
		String selected = Boundary.getPressedButton(
			player.getName() +
			"\nSelect an option", strings);
		int selection = 0;
		for(int i = 0; i < strings.length; i++) {
			String s = strings[i];
			if (s.equals(selected)) {
				selection = i;
			}
		}
		return options[selection];
	}

	private void buyHouse(Player player) {
		String[] strings = new String[40];
		int nextAvailableIndex = 0;
		for (int i = 0; i < player.getOwned().length; i++) {
			Ownable o = player.getOwned()[i];
			if(o instanceof Street){
				Street s = (Street)o;
				String str = s.getName() + " -";
				if(s.getHouseCount() == 1) str += " (1 house)";
				if(s.getHouseCount() > 1) str += " (" + s.getHouseCount() + " houses)";
				str += " Price: "+s.getHousePrice();
				boolean canAfford = 
						player.getBalance() >= s.getHousePrice();
				if(s.getHouseCount() < 5 && canAfford && !s.isPawned()){
					strings[nextAvailableIndex++] =  str;
				}
			}
		}
		if(nextAvailableIndex == 0) return;//No houses can be bought
		String[] options = new String[nextAvailableIndex];
		for (int i = 0; i < options.length; i++) { options[i] = strings[i]; }
		String selection = Boundary.getSelection(
				"On which lot do you wish to buy a house", options);
		if(selection == null) return;
		String streetname = selection.split(" -")[0].trim();
		Street selected = null; 
		for(Field f : Board.fields){
			if(f instanceof Street){
				Street s = (Street)f;
				if(s.getName().equalsIgnoreCase(streetname)){
					selected = s;
					break;
				}
			}
		}
		if(selected == null) return;
		int houseCount = selected.getHouseCount();
		selected.setHouseCount(houseCount + 1);
		if(selected.getHouseCount() == 5){
			Boundary.setHotel(selected.getNumber(), true);
		}else{
			Boundary.setHouses(selected.getNumber(), selected.getHouseCount());
		}
		player.withdraw(selected.getHousePrice());
	}
	private void sellHouse(Player player) {
		String[] strings = new String[40];
		int nextAvailableIndex = 0;
		for (int i = 0; i < player.getOwned().length; i++) {
			Ownable o = player.getOwned()[i];
			if(o instanceof Street){
				Street s = (Street)o;
				String str = s.getName() + " -";
				if(s.getHouseCount() == 1) str += " (1 house)";
				if(s.getHouseCount() > 1 && s.getHouseCount() < 5) str += " (" + s.getHouseCount() + " houses)";
				if(s.getHouseCount() == 5) str += " (Hotel)";
				str += " Sale price: "+s.getHousePrice() / 2; //OBS sell value = half price
				
				if(s.getHouseCount() > 0){
					strings[nextAvailableIndex++] =  str;
				}
			}
		}
		String[] options = new String[nextAvailableIndex];
		for (int i = 0; i < options.length; i++) { options[i] = strings[i]; }
		String selection = Boundary.getSelection(
				"On which lot do you wish to sell a house", options);
		if(selection == null) return;
		String streetname = selection.split(" -")[0].trim();
		Street selected = null; 
		for(Field f : Board.fields){
			if(f instanceof Street){
				Street s = (Street)f;
				if(s.getName().equalsIgnoreCase(streetname)){
					selected = s;
					break;
				}
			}
		}
		if(selected == null) return;
		int houseCount = selected.getHouseCount();
		selected.setHouseCount(houseCount - 1);
		Boundary.setHouses(selected.getNumber(), selected.getHouseCount());
		player.deposit(selected.getHousePrice() / 2);
	}
	private void rollDice(Player player) {
		Board.cup.roll();
		int sum = Board.cup.getSum();
		rollDice(player, sum);
	}
	private void rollDice(Player player, int sum) {
		this.hasRolled = true;
		if(Board.cup.isSame()){
			this.doubleCounter++;
			this.hasRolled = false;
		}
		if(this.doubleCounter >= 3){
//			hasRolled = true;
			this.endTurn = true;
			Board.fields[Board.GOTOJAILPOSITION - 1].landOnField(player);
			return;
		}
		int position = player.getPosition() + sum;
		if(position > 40){
			position -= 40;
			player.deposit(Board.startBonus);
			Boundary.showMessage(
					"You have crossed start and received a " +
					"start bonus of "+Board.startBonus);
		}
		player.setPosition(position);
		Board.fields[player.getPosition() - 1].landOnField(player);
	}
	private void pawn(Player player) {
		String[] strings = new String[40];
		int nextAvailableIndex = 0;
		for (int i = 0; i < player.getOwned().length; i++) {
			Ownable o = player.getOwned()[i];
			if (o != null) {
				String str = o.getName() + " - " + o.getPrice() / 2;
				boolean noHouses = true;
				if(o instanceof Street){
					Street s = (Street)o;
					noHouses = s.getHouseCount() <= 0;
				}
				if(noHouses && !o.isPawned()){
					strings[nextAvailableIndex++] =  str;
				}
			}
		}
		if(nextAvailableIndex == 0) return;//No lots can be pawned.
		String[] options = new String[nextAvailableIndex];
		for (int i = 0; i < options.length; i++) { options[i] = strings[i]; }
		String selection = Boundary.getSelection(
				"Which lot do you wish to pawn", options);
		if(selection == null) return;
		String streetname = selection.split(" - ")[0].trim();
		Ownable selected = null; 
		for(Field f : Board.fields){
			if(f instanceof Ownable){
				Ownable o = (Ownable)f;
				if(o.getName().equalsIgnoreCase(streetname)){
					selected = o;
					break;
				}
			}
		}
		if(selected == null) return;
		selected.setPawned(true);
		player.deposit(selected.getPrice() / 2);
	}
	private void unPawn(Player player) {
		String[] strings = new String[40];
		int nextAvailableIndex = 0;
		for (int i = 0; i < player.getOwned().length; i++) {
			Ownable o = player.getOwned()[i];
			if (o != null) {
				String str = o.getName() + " - " + (int)((o.getPrice() / 2)*1.1);
				if(o.isPawned()){
					strings[nextAvailableIndex++] =  str;
				}
			}
		}
		if(nextAvailableIndex == 0) return;//No streets to unPawn
		String[] options = new String[nextAvailableIndex];
		for (int i = 0; i < options.length; i++) { options[i] = strings[i]; }
		String selection = Boundary.getSelection(
				"Which lot do you wish to unpawn", options);
		if(selection == null) return;
		String streetname = selection.split(" - ")[0].trim();
		Ownable selected = null; 
		for(Field f : Board.fields){
			if(f instanceof Ownable){
				Ownable o = (Ownable)f;
				if(o.getName().equalsIgnoreCase(streetname)){
					selected = o;
					break;
				}
			}
		}
		if(selected == null) return;
		selected.setPawned(false);
		player.withdraw((int)((selected.getPrice() / 2)*1.1)); // deduct 10 percent in interest.
	}
	
	private boolean canBuild(Player player) {
		for (Ownable o : player.getOwned()) {
			if(o != null 
					&& o instanceof Street 
					&& ((Street)o).getHouseCount() < 5
					&& !o.isPawned()
					&& ((Street)o).getHousePrice() <= player.getBalance()){
				return true;
			}
		}
		return false;
	}
	private boolean hasSellableHouses(Player player) {
		for (Ownable o : player.getOwned()) {
			if(o != null 
					&& o instanceof Street 
					&& ((Street)o).getHouseCount() > 0){
				return true;
			}
		}
		return false;
	}
	private boolean canPawn(Player player) {
		for (Ownable o : player.getOwned()) {
			boolean noHouses = true;
			if (o instanceof Street){
				noHouses = ((Street)o).getHouseCount() <= 0;
			}
			if(o != null 
					&& !o.isPawned()
					&& noHouses){
				return true;
			}
		}
		return false; 
	}
	private boolean canUnPawn(Player player) {
		for (Ownable o : player.getOwned()) {
			if (o != null && o.isPawned()){ return true; }
		}
		return false;
	}
	private boolean canRollDice() {
		return !this.hasRolled; 
	}
	private boolean canEndTurn(Player player) {
		return !canRollDice() && player.calculatePlayerWorth() >= 0;
	}
}